package myTargetCodeGen;

import bit.minisys.minicc.icgen.Quat;
import bit.minisys.minicc.icgen.TemporaryValue;
import bit.minisys.minicc.parser.ast.ASTArrayAccess;
import bit.minisys.minicc.parser.ast.ASTIdentifier;
import bit.minisys.minicc.parser.ast.ASTIntegerConstant;
import bit.minisys.minicc.parser.ast.ASTNode;
import org.python.antlr.ast.Str;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ASMProgram {
    public List<String> config;
    public List<String> data;
    public List<String> code;
    public List<String> localData;
    public List<String> flagData;

    public String preJMPType = "";

    public Map<String, String> strConstMap;
    private int strCount = 0;

    //初始化
    public void ASMProgramInit()
    {
        strConstMap = new HashMap<>();
        //汇编设置
        config = new ArrayList<>();
        config.add(".386");
        config.add(".model flat, stdcall");
        config.add("option casemap:none");
        config.add("\n");
        config.add("includelib msvcrt.lib");
        config.add("includelib user32.lib");
        config.add("includelib kernel32.lib");
        config.add("\n");
        config.add("printf PROTO C : ptr sbyte, :VARARG");
        config.add("scanf PROTO C : ptr sbyte, :VARARG");
        config.add("\n");

        //数据段设置
        data = new ArrayList<>();
        data.add(".data");
        data.add("printIntFormat db '%d',0ah,0");
        data.add("printStrFormat db '%s',0ah,0");
        data.add("scanIntFormat db '%d',0");

        //代码段设置
        code = new ArrayList<>();
        code.add(".code");
        code.add("Mars_PrintStr PROC outStr:ptr sbyte");
        code.add("\tinvoke printf, offset printStrFormat, outStr");
        code.add("\tret");
        code.add("Mars_PrintStr endp");

        code.add("Mars_PrintInt PROC outInt:SDWORD");
        code.add("\tinvoke printf, offset printIntFormat, outInt");
        code.add("\tret");
        code.add("Mars_PrintInt endp");

        code.add("Mars_GetInt PROC");
        code.add("LOCAL tmp:SDWORD");
        code.add("\tinvoke scanf, offset scanIntFormat, ADDR tmp");
        code.add("\tmov eax, tmp");
        code.add("\tret");
        code.add("Mars_GetInt endp");

        localData = new ArrayList<>();
        flagData = new ArrayList<>();
    }

    public void addStrConst(String str)
    {
        if(!strConstMap.containsKey(str))
        {
            strConstMap.put(str, "StrConst" + "_" + strCount);
            data.add("StrConst" + "_" + strCount + " db " + str + ", 0");
            strCount++;
        }
    }

    public void ASMProgramOut(String outPath)
    {
        try{
            BufferedWriter out = new BufferedWriter(new FileWriter(outPath));
            code.add("AsmStart:");
            code.add("\tinvoke main");
            code.add("\tret");
            code.add("end AsmStart");

            List<String> targetCode = new ArrayList<>();
            targetCode.addAll(config);
            targetCode.addAll(data);
            targetCode.addAll(code);

            for (String line:targetCode) {
                out.write(line);
                out.write("\n");
            }

            out.close();
            System.out.println("7. Code Generation End");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String typeTransform(String cType)
    {
        String retType = new String();
        if(cType.equals("int"))
        {
            retType = "SDWORD";
        }
        return retType;
    }

    public void addBinaryQuatCode(Quat quat)
    {
        String op = quat.getOp();
        String opnd1 = getOpndID(quat.getOpnd1());
        String opnd2 = getOpndID(quat.getOpnd2());
        String res = getOpndID(quat.getRes());

        if(op.equals("="))
        {
            code.add("\tmov eax, " + opnd1);
            if(quat.getRes() instanceof TemporaryValue)
            {
                if(((TemporaryValue) quat.getRes()).actType == TemporaryValue.tmpType.MEMORY)
                    code.add("\tmov " + res + ", eax");
                else if (((TemporaryValue) quat.getRes()).actType == TemporaryValue.tmpType.EBX)
                {
                    code.add("\tmov [edi], eax");
                }
            }
            else
            {
                code.add("\tmov " + res + ", eax");
            }
        }
        else if(op.equals("+"))
        {
            code.add("\tmov eax, " + opnd1);
            code.add("\tadd eax, " + opnd2);
            code.add("\tmov " + res + ", eax");
        }
        else if(op.equals("-"))
        {
            code.add("\tmov eax, " + opnd1);
            code.add("\tsub eax, " + opnd2);
            code.add("\tmov " + res + ", eax");
        }
        else if(op.equals("*"))
        {
            code.add("\tmov eax, " + opnd1);
            code.add("\tcdq");
            code.add("\tmov ebx, " + opnd2);
            code.add("\timul ebx");
            code.add("\tmov " + res + ", eax");
        }
        else if(op.equals("%"))
        {
            code.add("\tmov eax, " + opnd1);
            code.add("\tcdq");
            code.add("\tmov ebx, " + opnd2);
            code.add("\tidiv ebx");
            code.add("\tmov " + res + ", edx");
        }
        else if(op.equals("/"))
        {
            code.add("\tmov eax, " + opnd1);
            code.add("\tcdq");
            code.add("\tmov ebx, " + opnd2);
            code.add("\tidiv ebx");
            code.add("\tmov " + res + ", eax");
        }
        else if(op.equals("<=") || op.equals(">=") || op.equals(">") || op.equals("<") || op.equals("==") || op.equals("!="))
        {
            code.add("\tmov eax, " + opnd1);
            code.add("\tmov ebx, " + opnd2);
            code.add("\tcmp eax, ebx");

            if(op.equals("<="))
            {
                preJMPType = "JG";
            }
            else if(op.equals(">="))
            {
                preJMPType = "JL";
            }
            else if(op.equals(">"))
            {
                preJMPType = "JLE";
            }
            else if(op.equals("<"))
            {
                preJMPType = "JGE";
            }
            else if(op.equals("=="))
            {
                preJMPType = "JNE";
            }
            else if(op.equals("!="))
            {
                preJMPType = "JE";
            }

        }
    }

    public void addPostfixQuatCode(Quat quat)
    {
        String op = quat.getOp();
        String opnd1 = getOpndID(quat.getOpnd1());
        String opnd2 = getOpndID(quat.getOpnd2());
        String res = getOpndID(quat.getRes());

        code.add("\tmov eax, " + opnd2);
        code.add("\tmov " + res + ", " + "eax");

        if(op.equals("++"))
        {
            code.add("\tinc " + opnd2);
        }
        else if(op.equals("--"))
        {
            code.add("\tdec " + opnd2);
        }

    }

    public void addArrayAccessQuatCode(Quat quat)
    {
        String op = quat.getOp();
        String opnd1 = getOpndID(quat.getOpnd1());
        String opnd2 = getOpndID(quat.getOpnd2());
        String res = getOpndID(quat.getRes());

        code.add("\tlea ebx" + ", " + opnd1);
        code.add("\tmov eax" + ", " + opnd2);
        code.add("\tcdq");
        code.add("\tmov ecx, 4");
        code.add("\timul ecx");
        code.add("\tadd eax, ebx");
        code.add("\tmov ebx, " + "eax");

        code.add("\tmov edx, [ebx]");
        code.add("\tmov " + res + ", " + "edx");
    }

    public void addTmpVar(TemporaryValue temporaryValue)
    {
        String varName = "tmpVar" + "_" + ((TemporaryValue)temporaryValue).id;
        localData.add("\tLOCAL " + varName + " : DWORD");
    }



    public String getLocalVar(ASTNode node)
    {
        if(node instanceof ASTIdentifier)
        {
            return "_" + ((ASTIdentifier) node).value;
        }
        else if(node instanceof ASTIntegerConstant)
        {
            return ((ASTIntegerConstant) node).value.toString();
        }
        else if(node instanceof TemporaryValue)
        {
            if(((TemporaryValue) node).actType == TemporaryValue.tmpType.EBX)
            {
                code.add("\tmov ebx, [ebx]");
                return "ebx";
            }
            else
            {
                return getTmpVarName((TemporaryValue) node);
            }
        }
        else
            return "";
    }

    public String getTmpVarName(TemporaryValue temporaryValue)
    {
        String varName = "tmpVar" + "_" + ((TemporaryValue)temporaryValue).id;
        return varName;
    }

    public void addFlagLabel(TemporaryValue temporaryValue)
    {
        String flagName = "Flag" + "_" + ((TemporaryValue)temporaryValue).id;
        flagData.add(flagName);
    }

    public String getFlagLabel(TemporaryValue temporaryValue)
    {
        String flagName = "Flag" + "_" + ((TemporaryValue)temporaryValue).id;
        return flagName;
    }


    public String getOpndID(ASTNode opnd)
    {
        if(opnd instanceof TemporaryValue)
        {
            return "tmpVar" + "_" + ((TemporaryValue)opnd).id;
        }
        if(opnd instanceof ASTIdentifier)
        {
            return "_" + ((ASTIdentifier)opnd).value;
        }
        if(opnd instanceof ASTIntegerConstant)
        {
            return ((ASTIntegerConstant) opnd).value.toString();
        }
        return "";
    }
}
